This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

library(lme4)
Loading required package: Matrix
library(ggplot2)
library(plotrix)
library(visreg)
library(stringr)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ tibble  3.1.0     ✓ purrr   0.3.4
✓ tidyr   1.1.2     ✓ dplyr   1.0.4
✓ readr   1.4.0     ✓ forcats 0.5.0
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x tidyr::expand() masks Matrix::expand()
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
x tidyr::pack()   masks Matrix::pack()
x tidyr::unpack() masks Matrix::unpack()
library(ggpubr)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'car':
  method                          from
  influence.merMod                lme4
  cooks.distance.influence.merMod lme4
  dfbeta.influence.merMod         lme4
  dfbetas.influence.merMod        lme4
library(gridExtra)

Attaching package: ‘gridExtra’

The following object is masked from ‘package:dplyr’:

    combine
library(tidyverse)
library(tidymodels)
── Attaching packages ─────────────────────────────────────────────────────────────────────────────────────────────────────────── tidymodels 0.1.2 ──
✓ broom     0.7.3      ✓ recipes   0.1.15
✓ dials     0.0.9      ✓ rsample   0.0.9 
✓ infer     0.5.4      ✓ tune      0.1.3 
✓ modeldata 0.1.0      ✓ workflows 0.2.2 
✓ parsnip   0.1.5      ✓ yardstick 0.0.8 
── Conflicts ────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidymodels_conflicts() ──
x gridExtra::combine() masks dplyr::combine()
x scales::discard()    masks purrr::discard()
x tidyr::expand()      masks Matrix::expand()
x dplyr::filter()      masks stats::filter()
x recipes::fixed()     masks stringr::fixed()
x dplyr::lag()         masks stats::lag()
x tidyr::pack()        masks Matrix::pack()
x yardstick::spec()    masks readr::spec()
x recipes::step()      masks stats::step()
x tidyr::unpack()      masks Matrix::unpack()
x recipes::update()    masks Matrix::update(), stats::update()
library(vip) 

Attaching package: ‘vip’

The following object is masked from ‘package:utils’:

    vi
library(plm)

Attaching package: ‘plm’

The following objects are masked from ‘package:dplyr’:

    between, lag, lead
lasso_output_data <- read_csv("/Users/noa/Workspace/lasso_positions_sampling_results/new_spr_results.csv")

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  Lasso_folder = col_character(),
  RAxML_baseline_run_prefix = col_character(),
  SPR_search_starting_tree_ll = col_logical(),
  SPR_search_starting_tree_newick = col_character(),
  alphas = col_character(),
  alternative_files_folder = col_character(),
  brlen_generator = col_character(),
  curr_job_folder = col_character(),
  curr_msa_version_folder = col_character(),
  dataset_id = col_character(),
  file_name = col_character(),
  file_type = col_character(),
  file_type_biopython = col_character(),
  jobs_prefix = col_character(),
  lasso_SPR_first_phase_tree_newick = col_character(),
  lasso_SPR_second_phase_tree_newick = col_character(),
  lasso_SPR_starting_tree_path = col_character(),
  lasso_baseline_run_prefix = col_character(),
  lasso_ll_per_iteration_first_phase = col_character(),
  lasso_ll_per_iteration_second_phase = col_character()
  # ... with 21 more columns
)
ℹ Use `spec()` for the full column specifications.
data_source <- read_csv("/Users/noa/Workspace/data/sampled_datasets.csv")
Missing column names filled in: 'X1' [1]
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  X1 = col_double(),
  path = col_character(),
  db = col_character(),
  ntaxa = col_double(),
  nchars = col_double()
)
lasso_output_data<-lasso_output_data%>%mutate(relative_path = str_replace_all(dataset_id,'(/groups/pupko/noaeker/data/ABC_DR/)|(/ref_msa.aa.phy)',""))
spr_data= merge(lasso_output_data,data_source,by.x="relative_path",by.y="path", all.x = TRUE)

if (nrow(spr_data)<nrow(lasso_output_data))
{print("Problem in matching path names")
  cat("nrow data=",nrow(spr_data), "nrow lasso=",nrow(lasso_output_data))
}
spr_data <- spr_data %>% mutate(sample_pct_rd = floor(as.integer(sample_pct*100)/5)*(5/100), delta_ll_first_phase = ifelse(rf_dist_first_phase>0,naive_SPR_ll-lasso_SPR_first_phase_ll,0), delta_ll_second_phase = ifelse(rf_dist_second_phase>0,naive_SPR_ll-lasso_SPR_second_phase_ll,0)) %>% filter(n_seq>=5)
spr_data %>% distinct(db,dataset_id) %>% group_by(db)%>% summarize(n=n())

plot for the example MSA

example_msa_data<- spr_data %>% filter (relative_path=="Selectome/Euteleostomi/ENSGT00660000095541", actucal_training_size==800)

example_msa_ll<- read_csv("/Users/noa/Workspace/lasso_positions_sampling_results/test_sitelh_df_prediction_grant.csv")
Missing column names filled in: 'X1' [1]
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  X1 = col_double(),
  predicted_test_ll = col_double(),
  true_test_ll = col_double()
)
g00<-ggplot(data=example_msa_data, aes(x=sample_pct, y=1-`lasso_test_R^2`)) +theme_classic()+
geom_line() +geom_point()+labs(title="")+xlab("Percentage of chosen positions")+ ylab("unexplained variance (%)") + theme(plot.title = element_text(hjust = 0.5))+scale_x_continuous(labels = scales::percent)+scale_y_continuous(labels = scales::percent)+ theme(plot.title = element_text(hjust = 0.5))+theme(axis.text=element_text(size=11),
        axis.title=element_text(size=11))

g01<-ggplot(example_msa_ll,aes(true_test_ll, predicted_test_ll)) +theme_classic()+
  geom_smooth(method='lm')+xlab("True LL")+ylab("Predicted LL ")+geom_point()+ labs(title="")+theme(plot.title = element_text(hjust = 0.5))+ theme(plot.title = element_text(hjust = 0.5))+theme(axis.text=element_text(size=11),
        axis.title=element_text(size=11))


# g02<-ggplot(data=example_MSA_data_lasso, aes(x=actucal_training_size,color=brlen_generator, y=sample_pct))+theme_classic()+ labs(color = "Branch length distribution")+
#      geom_line() +geom_point()+
#    xlab("Training size")+ylab("Positions (%)")+ #theme(axis.title=element_text(size=10),)
#   guides(fill=guide_legend(title="Branch length distribution"))+ggtitle("Median percentage of positions chosen by Lasso")+scale_y_continuous(labels = scales::percent)+ theme(legend.position = "none")+ theme(plot.title = element_text(hjust = 0.5))+labs(color = "Branch length distribution")
# 
ggarrange(g00, g01, hjust=-1,vjust=1, heights=c(2,2),common.legend=TRUE,
          labels = c("A", "B"),
          ncol = 1, nrow =2)
`geom_smooth()` using formula 'y ~ x'
`geom_smooth()` using formula 'y ~ x'

General histograms

spr_data_per_dataset <- spr_data %>% group_by(relative_path, gap_pct, divergence, constant_sites_pct, avg_entropy, n_seq, n_loci) %>%
  summarise(best_delta_ll =  min(delta_ll_first_phase))
`summarise()` has grouped output by 'relative_path', 'gap_pct', 'divergence', 'constant_sites_pct', 'avg_entropy', 'n_seq'. You can override using the `.groups` argument.
ggplot(data = spr_data_per_dataset, aes(n_seq)) +geom_histogram()

ggplot(data = spr_data_per_dataset, aes(n_loci)) +geom_histogram()

#ggplot(data = spr_data_per_dataset, aes(x=gap_pct)) +geom_histogram()
#ggplot(data = spr_data_per_dataset, aes(x=divergence)) +geom_histogram()
#ggplot(data = spr_data_per_dataset, aes(x=constant_sites_pct)) +geom_histogram()
#ggplot(data = spr_data_per_dataset, aes(x=avg_entropy)) +geom_histogram()
ggplot(data = spr_data_per_dataset, aes(x=best_delta_ll)) +geom_histogram()

NA
NA

Find datasets in which a better tree was found

good_datasets<-spr_data %>% filter(delta_ll_first_phase<0) %>% distinct(relative_path) %>% pull(relative_path)
good_datasets
[1] "Selectome/Euteleostomi/ENSGT00610000085715"
spr_data %>% filter(relative_path %in% good_datasets ) %>% dplyr::select(relative_path,n_seq, gap_pct,constant_sites_pct,divergence,avg_entropy, actucal_training_size,sample_pct, `R^2_pearson_during_tree_search`,delta_ll_first_phase,lasso_SPR_first_phase_spr_moves,lasso_SPR_second_phase_spr_moves,lasso_SPR_second_phase_ll, naive_SPR_spr_moves)
good_datasets
[1] "Selectome/Euteleostomi/ENSGT00610000085715"

Find datasets in which a much worse tree was found

problematic_datasets<-spr_data %>% filter(delta_ll_first_phase>500) %>% distinct(relative_path) %>% pull(relative_path)
problematic_datasets
[1] "Selectome/Euteleostomi/ENSGT00530000063613"
spr_data  %>% filter(relative_path %in% problematic_datasets) %>% dplyr::select(relative_path,n_seq, actucal_training_size,sample_pct, `R^2_pearson_during_tree_search`,delta_ll_first_phase,lasso_SPR_first_phase_spr_moves,lasso_SPR_second_phase_spr_moves,lasso_SPR_second_phase_ll, naive_SPR_spr_moves)
NA
NA
NA

Find datasets in which delta ll on second phase is greater than zero

not_best_final_tree_datasets<-spr_data %>% filter(delta_ll_second_phase>0) %>% distinct(relative_path) %>% pull(relative_path)
problematic_datasets
[1] "Selectome/Euteleostomi/ENSGT00530000063613"
spr_data  %>% filter(relative_path %in% not_best_final_tree_datasets) %>% dplyr::select(relative_path,n_seq, actucal_training_size,sample_pct, `R^2_pearson_during_tree_search`,delta_ll_first_phase,delta_ll_second_phase,rf_dist_first_phase,lasso_SPR_first_phase_spr_moves,lasso_SPR_second_phase_spr_moves,lasso_SPR_second_phase_ll,rf_dist_second_phase, naive_SPR_spr_moves)
NA
NA

Accuracy metrics as a function of training size and sample percentage



#spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=as.factor(paste(actucal_training_size,"\n",sample_pct_rd)),y #=lasso_test_spearmanr )) + geom_boxplot() + labs(x="training_size\nsmple_pct")

spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=as.factor(sample_pct_rd),y =delta_ll_first_phase, fill = as.factor(sample_pct_rd) )) + geom_boxplot()+ labs(x="smple_pct") + facet_wrap(~actucal_training_size)

spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=as.factor(sample_pct_rd),y =lasso_test_spearmanr,fill = as.factor(sample_pct_rd) )) + geom_boxplot()+ labs(x="smple_pct") + facet_wrap(~actucal_training_size)

spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=as.factor(sample_pct_rd),y =`lasso_test_R^2`,fill = as.factor(sample_pct_rd) )) + geom_boxplot()+ labs(x="smple_pct") + facet_wrap(~actucal_training_size)

spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=as.factor(sample_pct_rd),y =`R^2_pearson_during_tree_search`,fill = as.factor(sample_pct_rd) )) + geom_boxplot()+ labs(x="smple_pct") + facet_wrap(~actucal_training_size)

NA
NA
NA
NA
NA

minimal training size required for best accuracy + minimum sample percentage required for best accuracy vs. sample percentage

spr_data_ok <- spr_data# %>%   filter(!(relative_path %in% problematic_datasets))
spr_data_ok_best_rows<- spr_data_ok %>% group_by(relative_path) %>%  summarise (delta_ll_first_phase =min(delta_ll_first_phase)) 
spr_full_data_best_rows<-spr_data_ok_best_data <- spr_data_ok %>% semi_join(spr_data_ok_best_rows) 
Joining, by = c("relative_path", "delta_ll_first_phase")
spr_data_ok_best_data <- spr_full_data_best_rows %>% group_by(relative_path,n_loci, delta_ll_first_phase, n_seq,avg_entropy, gap_pct, constant_sites_pct) 
min_sample_pct_data <- spr_data_ok_best_data %>% summarise(min_sample_pct = min(sample_pct_rd ))
`summarise()` has grouped output by 'relative_path', 'n_loci', 'delta_ll_first_phase', 'n_seq', 'avg_entropy', 'gap_pct'. You can override using the `.groups` argument.
min_sample_pct_data %>% ggplot(aes(x=as.factor(n_seq),y=min_sample_pct)) + scale_y_continuous(labels = scales::percent)+ geom_boxplot()

min_training_size_data <- spr_data_ok_best_data %>% summarise(min_training_size = min(actucal_training_size))
`summarise()` has grouped output by 'relative_path', 'n_loci', 'delta_ll_first_phase', 'n_seq', 'avg_entropy', 'gap_pct'. You can override using the `.groups` argument.
min_training_size_data %>% ggplot(aes(x=as.factor(n_seq),y=min_training_size )) + geom_boxplot()


#test.training.size<-spr_data_ok_best_data %>% summarise(min_training_size = min(actucal_training_size))  
#test.sample.pct<-spr_data_ok_best_data %>% summarise(min_sample_pct = min(sample_pct))
#lm.training.size<-lm(log(min_training_size)~log(n_loci)+n_seq+avg_entropy+ gap_pct+constant_sites_pct, data=test.training.size)
#lm.sample.pct<-lm(log(min_sample_pct)~log(n_loci)+n_seq+avg_entropy+ gap_pct+constant_sites_pct, data=test.sample.pct)

#summary(lm.training.size)
#summary(lm.sample.pct)
spr_data_ok_best_rows
spr_full_data_best_rows %>% dplyr::select(relative_path,n_loci, actucal_training_size, sample_pct_rd,delta_ll_first_phase)

Focusing on datasets with 15 sequences, what is the best cuttoff?


spr_data %>%filter(n_seq==15)%>% group_by(actucal_training_size, sample_pct_rd) %>% summarize(pct_identical = sum(rf_dist_first_phase==0)/sum(rf_dist_first_phase==0 | rf_dist_first_phase>0), identical_topo= sum(rf_dist_first_phase==0), diff_topo = sum(rf_dist_first_phase>0), max_rf = max(rf_dist_first_phase), max_delta_ll= max(delta_ll_first_phase),median_delta_ll= median(delta_ll_first_phase[rf_dist_first_phase>0]),mean_delta_ll = mean(delta_ll_first_phase[rf_dist_first_phase>0]), max_spr_dist = max(lasso_SPR_second_phase_spr_moves), )
`summarise()` has grouped output by 'actucal_training_size'. You can override using the `.groups` argument.
spr_data %>%filter(n_seq==15) %>% filter(!(relative_path %in% problematic_datasets)) %>% filter(rf_dist_first_phase>0) %>% ggplot(aes(x=as.factor(sample_pct_rd),y= delta_ll_first_phase)) + geom_boxplot() + facet_wrap(~as.factor(actucal_training_size))

NA
NA
NA
NA

Focusing on datasets with less than 15 sequences, what is the best cuttoff?


spr_data %>%filter(n_seq<15)%>% group_by(actucal_training_size, sample_pct_rd) %>% summarize(pct_identical = sum(rf_dist_first_phase==0)/sum(rf_dist_first_phase==0 | rf_dist_first_phase>0), identical_topo= sum(rf_dist_first_phase==0), diff_topo = sum(rf_dist_first_phase>0), max_rf = max(rf_dist_first_phase), max_delta_ll= max(delta_ll_first_phase),median_delta_ll= median(delta_ll_first_phase[rf_dist_first_phase>0]),mean_delta_ll = mean(delta_ll_first_phase[rf_dist_first_phase>0]), max_spr_dist = max(lasso_SPR_second_phase_spr_moves), )
`summarise()` has grouped output by 'actucal_training_size'. You can override using the `.groups` argument.
spr_data %>%filter(n_seq<15) %>% filter(rf_dist_first_phase>0) %>% ggplot(aes(x=as.factor(sample_pct_rd),y= delta_ll_first_phase)) + geom_boxplot() +facet_wrap(~as.factor(actucal_training_size))

NA
NA
NA
NA

spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=`lasso_test_R^2`,y =`R^2_pearson_during_tree_search` )) +geom_point()


print(cor(spr_data$`lasso_test_R^2`, spr_data$`R^2_pearson_during_tree_search`))
[1] 0.2751959
spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=`lasso_test_R^2`,y = delta_ll_first_phase )) +geom_point()


print(cor(spr_data$`lasso_test_R^2`, spr_data$delta_ll_first_phase))
[1] -0.2942886
spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x=`R^2_pearson_during_tree_search`,y = delta_ll_first_phase )) +geom_point()


print(cor(spr_data$`R^2_pearson_during_tree_search`, spr_data$delta_ll_first_phase ))
[1] -0.6809226
spr_data %>% filter(!relative_path %in% problematic_datasets) %>% ggplot(aes(x= mistake_cnt,y = delta_ll_first_phase )) +geom_point()


print(cor(spr_data$mistake_cnt, spr_data$delta_ll_first_phase ))
[1] 0.2461368

Looking at each dataset separately. How delta ll changes with training size and sample pct


for (dataset in unique(spr_data$relative_path)) {
   
print(spr_data %>%filter(relative_path==dataset ) %>% ggplot(aes(x=sample_pct,y=delta_ll_first_phase, group = as.factor(actucal_training_size), colour = as.factor(actucal_training_size))) + geom_line() + geom_point()+ scale_x_continuous(labels = scales::percent)) + labs(title=dataset)
     
}

Looking at each dataset separately. How R2 changes with training size and sample pct

for (dataset in unique(spr_data$relative_path)) {
   
print(spr_data %>%filter(relative_path==dataset ) %>% ggplot(aes(x=sample_pct,y=`lasso_test_R^2`, group = as.factor(actucal_training_size), colour = as.factor(actucal_training_size))) + geom_line() + geom_point()+ scale_x_continuous(labels = scales::percent)) + labs(title=dataset)
     
}

All datasets with 15 sequences



spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==800) & (n_seq==15)) %>% ggplot(aes(x=sample_pct_rd,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 800")


spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==400) & (n_seq==15)) %>% ggplot(aes(x=sample_pct_rd,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 400")


spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==200) & n_seq==15) %>% ggplot(aes(x=sample_pct_rd,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 200")


spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==100) & n_seq==15) %>% ggplot(aes(x=sample_pct_rd,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 100")

NA
NA
NA

Less than 15 sequences



spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==800) & (n_seq<15)) %>% ggplot(aes(x=sample_pct,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_point() + geom_line()+ theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 800")


spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==400) & (n_seq<15)) %>% ggplot(aes(x=sample_pct,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line()+ geom_point() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 400")


spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==200) & n_seq<15) %>% ggplot(aes(x=sample_pct,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line()+ geom_point() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 200")


spr_data %>%filter(!(relative_path %in% problematic_datasets) & (actucal_training_size==100) & n_seq<15) %>% ggplot(aes(x=sample_pct,y=delta_ll_first_phase, group= relative_path, colour = relative_path)) + geom_line()+ geom_point() + theme(legend.position = "none") + scale_x_continuous(labels = scales::percent)+labs(title="training size 100")

NA
NA
NA

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Cmd+Option+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Cmd+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpgYGB7cn0KbGlicmFyeShsbWU0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGxvdHJpeCkKbGlicmFyeSh2aXNyZWcpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeW1vZGVscykKbGlicmFyeSh2aXApIApsaWJyYXJ5KHBsbSkKCmBgYAoKCmBgYHtyfQpsYXNzb19vdXRwdXRfZGF0YSA8LSByZWFkX2NzdigiL1VzZXJzL25vYS9Xb3Jrc3BhY2UvbGFzc29fcG9zaXRpb25zX3NhbXBsaW5nX3Jlc3VsdHMvbmV3X3Nwcl9yZXN1bHRzLmNzdiIpCmRhdGFfc291cmNlIDwtIHJlYWRfY3N2KCIvVXNlcnMvbm9hL1dvcmtzcGFjZS9kYXRhL3NhbXBsZWRfZGF0YXNldHMuY3N2IikKbGFzc29fb3V0cHV0X2RhdGE8LWxhc3NvX291dHB1dF9kYXRhJT4lbXV0YXRlKHJlbGF0aXZlX3BhdGggPSBzdHJfcmVwbGFjZV9hbGwoZGF0YXNldF9pZCwnKC9ncm91cHMvcHVwa28vbm9hZWtlci9kYXRhL0FCQ19EUi8pfCgvcmVmX21zYS5hYS5waHkpJywiIikpCnNwcl9kYXRhPSBtZXJnZShsYXNzb19vdXRwdXRfZGF0YSxkYXRhX3NvdXJjZSxieS54PSJyZWxhdGl2ZV9wYXRoIixieS55PSJwYXRoIiwgYWxsLnggPSBUUlVFKQoKaWYgKG5yb3coc3ByX2RhdGEpPG5yb3cobGFzc29fb3V0cHV0X2RhdGEpKQp7cHJpbnQoIlByb2JsZW0gaW4gbWF0Y2hpbmcgcGF0aCBuYW1lcyIpCiAgY2F0KCJucm93IGRhdGE9Iixucm93KHNwcl9kYXRhKSwgIm5yb3cgbGFzc289Iixucm93KGxhc3NvX291dHB1dF9kYXRhKSkKfQpgYGAKYGBge3J9CnNwcl9kYXRhIDwtIHNwcl9kYXRhICU+JSBtdXRhdGUoc2FtcGxlX3BjdF9yZCA9IGZsb29yKGFzLmludGVnZXIoc2FtcGxlX3BjdCoxMDApLzUpKig1LzEwMCksIGRlbHRhX2xsX2ZpcnN0X3BoYXNlID0gaWZlbHNlKHJmX2Rpc3RfZmlyc3RfcGhhc2U+MCxuYWl2ZV9TUFJfbGwtbGFzc29fU1BSX2ZpcnN0X3BoYXNlX2xsLDApLCBkZWx0YV9sbF9zZWNvbmRfcGhhc2UgPSBpZmVsc2UocmZfZGlzdF9zZWNvbmRfcGhhc2U+MCxuYWl2ZV9TUFJfbGwtbGFzc29fU1BSX3NlY29uZF9waGFzZV9sbCwwKSkgJT4lIGZpbHRlcihuX3NlcT49NSkKCmBgYAoKYGBge3J9CnNwcl9kYXRhICU+JSBkaXN0aW5jdChkYixkYXRhc2V0X2lkKSAlPiUgZ3JvdXBfYnkoZGIpJT4lIHN1bW1hcml6ZShuPW4oKSkKYGBgCgpwbG90IGZvciB0aGUgZXhhbXBsZSBNU0EgCgpgYGB7cn0KZXhhbXBsZV9tc2FfZGF0YTwtIHNwcl9kYXRhICU+JSBmaWx0ZXIgKHJlbGF0aXZlX3BhdGg9PSJTZWxlY3RvbWUvRXV0ZWxlb3N0b21pL0VOU0dUMDA2NjAwMDAwOTU1NDEiLCBhY3R1Y2FsX3RyYWluaW5nX3NpemU9PTgwMCkKCmV4YW1wbGVfbXNhX2xsPC0gcmVhZF9jc3YoIi9Vc2Vycy9ub2EvV29ya3NwYWNlL2xhc3NvX3Bvc2l0aW9uc19zYW1wbGluZ19yZXN1bHRzL3Rlc3Rfc2l0ZWxoX2RmX3ByZWRpY3Rpb25fZ3JhbnQuY3N2IikKCmcwMDwtZ2dwbG90KGRhdGE9ZXhhbXBsZV9tc2FfZGF0YSwgYWVzKHg9c2FtcGxlX3BjdCwgeT0xLWBsYXNzb190ZXN0X1JeMmApKSArdGhlbWVfY2xhc3NpYygpKwpnZW9tX2xpbmUoKSArZ2VvbV9wb2ludCgpK2xhYnModGl0bGU9IiIpK3hsYWIoIlBlcmNlbnRhZ2Ugb2YgY2hvc2VuIHBvc2l0aW9ucyIpKyB5bGFiKCJ1bmV4cGxhaW5lZCB2YXJpYW5jZSAoJSkiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKStzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KStzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpK3RoZW1lKGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMSkpCgpnMDE8LWdncGxvdChleGFtcGxlX21zYV9sbCxhZXModHJ1ZV90ZXN0X2xsLCBwcmVkaWN0ZWRfdGVzdF9sbCkpICt0aGVtZV9jbGFzc2ljKCkrCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpK3hsYWIoIlRydWUgTEwiKSt5bGFiKCJQcmVkaWN0ZWQgTEwgIikrZ2VvbV9wb2ludCgpKyBsYWJzKHRpdGxlPSIiKSt0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkrIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSt0aGVtZShheGlzLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTEpLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTEpKQoKCiMgZzAyPC1nZ3Bsb3QoZGF0YT1leGFtcGxlX01TQV9kYXRhX2xhc3NvLCBhZXMoeD1hY3R1Y2FsX3RyYWluaW5nX3NpemUsY29sb3I9YnJsZW5fZ2VuZXJhdG9yLCB5PXNhbXBsZV9wY3QpKSt0aGVtZV9jbGFzc2ljKCkrIGxhYnMoY29sb3IgPSAiQnJhbmNoIGxlbmd0aCBkaXN0cmlidXRpb24iKSsKIyAgICAgIGdlb21fbGluZSgpICtnZW9tX3BvaW50KCkrCiMgICAgeGxhYigiVHJhaW5pbmcgc2l6ZSIpK3lsYWIoIlBvc2l0aW9ucyAoJSkiKSsgI3RoZW1lKGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCkKIyAgIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZCh0aXRsZT0iQnJhbmNoIGxlbmd0aCBkaXN0cmlidXRpb24iKSkrZ2d0aXRsZSgiTWVkaWFuIHBlcmNlbnRhZ2Ugb2YgcG9zaXRpb25zIGNob3NlbiBieSBMYXNzbyIpK3NjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkrbGFicyhjb2xvciA9ICJCcmFuY2ggbGVuZ3RoIGRpc3RyaWJ1dGlvbiIpCiMgCmdnYXJyYW5nZShnMDAsIGcwMSwgaGp1c3Q9LTEsdmp1c3Q9MSwgaGVpZ2h0cz1jKDIsMiksY29tbW9uLmxlZ2VuZD1UUlVFLAogICAgICAgICAgbGFiZWxzID0gYygiQSIsICJCIiksCiAgICAgICAgICBuY29sID0gMSwgbnJvdyA9MikKCgoKCmBgYAoKR2VuZXJhbCBoaXN0b2dyYW1zCgpgYGB7cn0Kc3ByX2RhdGFfcGVyX2RhdGFzZXQgPC0gc3ByX2RhdGEgJT4lIGdyb3VwX2J5KHJlbGF0aXZlX3BhdGgsIGdhcF9wY3QsIGRpdmVyZ2VuY2UsIGNvbnN0YW50X3NpdGVzX3BjdCwgYXZnX2VudHJvcHksIG5fc2VxLCBuX2xvY2kpICU+JQogIHN1bW1hcmlzZShiZXN0X2RlbHRhX2xsID0gIG1pbihkZWx0YV9sbF9maXJzdF9waGFzZSkpCgpnZ3Bsb3QoZGF0YSA9IHNwcl9kYXRhX3Blcl9kYXRhc2V0LCBhZXMobl9zZXEpKSArZ2VvbV9oaXN0b2dyYW0oKQpnZ3Bsb3QoZGF0YSA9IHNwcl9kYXRhX3Blcl9kYXRhc2V0LCBhZXMobl9sb2NpKSkgK2dlb21faGlzdG9ncmFtKCkKI2dncGxvdChkYXRhID0gc3ByX2RhdGFfcGVyX2RhdGFzZXQsIGFlcyh4PWdhcF9wY3QpKSArZ2VvbV9oaXN0b2dyYW0oKQojZ2dwbG90KGRhdGEgPSBzcHJfZGF0YV9wZXJfZGF0YXNldCwgYWVzKHg9ZGl2ZXJnZW5jZSkpICtnZW9tX2hpc3RvZ3JhbSgpCiNnZ3Bsb3QoZGF0YSA9IHNwcl9kYXRhX3Blcl9kYXRhc2V0LCBhZXMoeD1jb25zdGFudF9zaXRlc19wY3QpKSArZ2VvbV9oaXN0b2dyYW0oKQojZ2dwbG90KGRhdGEgPSBzcHJfZGF0YV9wZXJfZGF0YXNldCwgYWVzKHg9YXZnX2VudHJvcHkpKSArZ2VvbV9oaXN0b2dyYW0oKQpnZ3Bsb3QoZGF0YSA9IHNwcl9kYXRhX3Blcl9kYXRhc2V0LCBhZXMoeD1iZXN0X2RlbHRhX2xsKSkgK2dlb21faGlzdG9ncmFtKCkKCgoKCgpgYGAKRmluZCBkYXRhc2V0cyBpbiB3aGljaCBhIGJldHRlciB0cmVlIHdhcyBmb3VuZAoKYGBge3J9Cmdvb2RfZGF0YXNldHM8LXNwcl9kYXRhICU+JSBmaWx0ZXIoZGVsdGFfbGxfZmlyc3RfcGhhc2U8MCkgJT4lIGRpc3RpbmN0KHJlbGF0aXZlX3BhdGgpICU+JSBwdWxsKHJlbGF0aXZlX3BhdGgpCmdvb2RfZGF0YXNldHMKc3ByX2RhdGEgJT4lIGZpbHRlcihyZWxhdGl2ZV9wYXRoICVpbiUgZ29vZF9kYXRhc2V0cyApICU+JSBkcGx5cjo6c2VsZWN0KHJlbGF0aXZlX3BhdGgsbl9zZXEsIGdhcF9wY3QsY29uc3RhbnRfc2l0ZXNfcGN0LGRpdmVyZ2VuY2UsYXZnX2VudHJvcHksIGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSxzYW1wbGVfcGN0LCBgUl4yX3BlYXJzb25fZHVyaW5nX3RyZWVfc2VhcmNoYCxkZWx0YV9sbF9maXJzdF9waGFzZSxsYXNzb19TUFJfZmlyc3RfcGhhc2Vfc3ByX21vdmVzLGxhc3NvX1NQUl9zZWNvbmRfcGhhc2Vfc3ByX21vdmVzLGxhc3NvX1NQUl9zZWNvbmRfcGhhc2VfbGwsIG5haXZlX1NQUl9zcHJfbW92ZXMpCmdvb2RfZGF0YXNldHMKCmBgYAoKRmluZCBkYXRhc2V0cyBpbiB3aGljaCBhIG11Y2ggd29yc2UgdHJlZSB3YXMgZm91bmQKYGBge3J9CnByb2JsZW1hdGljX2RhdGFzZXRzPC1zcHJfZGF0YSAlPiUgZmlsdGVyKGRlbHRhX2xsX2ZpcnN0X3BoYXNlPjUwMCkgJT4lIGRpc3RpbmN0KHJlbGF0aXZlX3BhdGgpICU+JSBwdWxsKHJlbGF0aXZlX3BhdGgpCnByb2JsZW1hdGljX2RhdGFzZXRzCnNwcl9kYXRhICAlPiUgZmlsdGVyKHJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJT4lIGRwbHlyOjpzZWxlY3QocmVsYXRpdmVfcGF0aCxuX3NlcSwgYWN0dWNhbF90cmFpbmluZ19zaXplLHNhbXBsZV9wY3QsIGBSXjJfcGVhcnNvbl9kdXJpbmdfdHJlZV9zZWFyY2hgLGRlbHRhX2xsX2ZpcnN0X3BoYXNlLGxhc3NvX1NQUl9maXJzdF9waGFzZV9zcHJfbW92ZXMsbGFzc29fU1BSX3NlY29uZF9waGFzZV9zcHJfbW92ZXMsbGFzc29fU1BSX3NlY29uZF9waGFzZV9sbCwgbmFpdmVfU1BSX3Nwcl9tb3ZlcykKCgoKYGBgCgpGaW5kIGRhdGFzZXRzIGluIHdoaWNoIGRlbHRhIGxsIG9uIHNlY29uZCBwaGFzZSBpcyBncmVhdGVyIHRoYW4gemVybwoKYGBge3J9Cm5vdF9iZXN0X2ZpbmFsX3RyZWVfZGF0YXNldHM8LXNwcl9kYXRhICU+JSBmaWx0ZXIoZGVsdGFfbGxfc2Vjb25kX3BoYXNlPjApICU+JSBkaXN0aW5jdChyZWxhdGl2ZV9wYXRoKSAlPiUgcHVsbChyZWxhdGl2ZV9wYXRoKQpwcm9ibGVtYXRpY19kYXRhc2V0cwpzcHJfZGF0YSAgJT4lIGZpbHRlcihyZWxhdGl2ZV9wYXRoICVpbiUgbm90X2Jlc3RfZmluYWxfdHJlZV9kYXRhc2V0cykgJT4lIGRwbHlyOjpzZWxlY3QocmVsYXRpdmVfcGF0aCxuX3NlcSwgYWN0dWNhbF90cmFpbmluZ19zaXplLHNhbXBsZV9wY3QsIGBSXjJfcGVhcnNvbl9kdXJpbmdfdHJlZV9zZWFyY2hgLGRlbHRhX2xsX2ZpcnN0X3BoYXNlLGRlbHRhX2xsX3NlY29uZF9waGFzZSxyZl9kaXN0X2ZpcnN0X3BoYXNlLGxhc3NvX1NQUl9maXJzdF9waGFzZV9zcHJfbW92ZXMsbGFzc29fU1BSX3NlY29uZF9waGFzZV9zcHJfbW92ZXMsbGFzc29fU1BSX3NlY29uZF9waGFzZV9sbCxyZl9kaXN0X3NlY29uZF9waGFzZSwgbmFpdmVfU1BSX3Nwcl9tb3ZlcykKCgpgYGAKCkFjY3VyYWN5IG1ldHJpY3MgYXMgYSBmdW5jdGlvbiBvZiB0cmFpbmluZyBzaXplIGFuZCBzYW1wbGUgcGVyY2VudGFnZQoKYGBge3J9CgoKI3Nwcl9kYXRhICU+JSBmaWx0ZXIoIXJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJT4lIGdncGxvdChhZXMoeD1hcy5mYWN0b3IocGFzdGUoYWN0dWNhbF90cmFpbmluZ19zaXplLCJcbiIsc2FtcGxlX3BjdF9yZCkpLHkgIz1sYXNzb190ZXN0X3NwZWFybWFuciApKSArIGdlb21fYm94cGxvdCgpICsgbGFicyh4PSJ0cmFpbmluZ19zaXplXG5zbXBsZV9wY3QiKQoKc3ByX2RhdGEgJT4lIGZpbHRlcighcmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAlPiUgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihzYW1wbGVfcGN0X3JkKSx5ID1kZWx0YV9sbF9maXJzdF9waGFzZSwgZmlsbCA9IGFzLmZhY3RvcihzYW1wbGVfcGN0X3JkKSApKSArIGdlb21fYm94cGxvdCgpKyBsYWJzKHg9InNtcGxlX3BjdCIpICsgZmFjZXRfd3JhcCh+YWN0dWNhbF90cmFpbmluZ19zaXplKQpzcHJfZGF0YSAlPiUgZmlsdGVyKCFyZWxhdGl2ZV9wYXRoICVpbiUgcHJvYmxlbWF0aWNfZGF0YXNldHMpICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKHNhbXBsZV9wY3RfcmQpLHkgPWxhc3NvX3Rlc3Rfc3BlYXJtYW5yLGZpbGwgPSBhcy5mYWN0b3Ioc2FtcGxlX3BjdF9yZCkgKSkgKyBnZW9tX2JveHBsb3QoKSsgbGFicyh4PSJzbXBsZV9wY3QiKSArIGZhY2V0X3dyYXAofmFjdHVjYWxfdHJhaW5pbmdfc2l6ZSkKc3ByX2RhdGEgJT4lIGZpbHRlcighcmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAlPiUgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihzYW1wbGVfcGN0X3JkKSx5ID1gbGFzc29fdGVzdF9SXjJgLGZpbGwgPSBhcy5mYWN0b3Ioc2FtcGxlX3BjdF9yZCkgKSkgKyBnZW9tX2JveHBsb3QoKSsgbGFicyh4PSJzbXBsZV9wY3QiKSArIGZhY2V0X3dyYXAofmFjdHVjYWxfdHJhaW5pbmdfc2l6ZSkKc3ByX2RhdGEgJT4lIGZpbHRlcighcmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAlPiUgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihzYW1wbGVfcGN0X3JkKSx5ID1gUl4yX3BlYXJzb25fZHVyaW5nX3RyZWVfc2VhcmNoYCxmaWxsID0gYXMuZmFjdG9yKHNhbXBsZV9wY3RfcmQpICkpICsgZ2VvbV9ib3hwbG90KCkrIGxhYnMoeD0ic21wbGVfcGN0IikgKyBmYWNldF93cmFwKH5hY3R1Y2FsX3RyYWluaW5nX3NpemUpCgoKCgoKYGBgCgoKbWluaW1hbCB0cmFpbmluZyBzaXplIHJlcXVpcmVkIGZvciBiZXN0IGFjY3VyYWN5ICsgbWluaW11bSBzYW1wbGUgcGVyY2VudGFnZSByZXF1aXJlZCBmb3IgYmVzdCBhY2N1cmFjeSB2cy4gc2FtcGxlIHBlcmNlbnRhZ2UKCmBgYHtyfQpzcHJfZGF0YV9vayA8LSBzcHJfZGF0YSMgJT4lICAgZmlsdGVyKCEocmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSkKc3ByX2RhdGFfb2tfYmVzdF9yb3dzPC0gc3ByX2RhdGFfb2sgJT4lIGdyb3VwX2J5KHJlbGF0aXZlX3BhdGgpICU+JSAgc3VtbWFyaXNlIChkZWx0YV9sbF9maXJzdF9waGFzZSA9bWluKGRlbHRhX2xsX2ZpcnN0X3BoYXNlKSkgCnNwcl9mdWxsX2RhdGFfYmVzdF9yb3dzPC1zcHJfZGF0YV9va19iZXN0X2RhdGEgPC0gc3ByX2RhdGFfb2sgJT4lIHNlbWlfam9pbihzcHJfZGF0YV9va19iZXN0X3Jvd3MpIApzcHJfZGF0YV9va19iZXN0X2RhdGEgPC0gc3ByX2Z1bGxfZGF0YV9iZXN0X3Jvd3MgJT4lIGdyb3VwX2J5KHJlbGF0aXZlX3BhdGgsbl9sb2NpLCBkZWx0YV9sbF9maXJzdF9waGFzZSwgbl9zZXEsYXZnX2VudHJvcHksIGdhcF9wY3QsIGNvbnN0YW50X3NpdGVzX3BjdCkgCm1pbl9zYW1wbGVfcGN0X2RhdGEgPC0gc3ByX2RhdGFfb2tfYmVzdF9kYXRhICU+JSBzdW1tYXJpc2UobWluX3NhbXBsZV9wY3QgPSBtaW4oc2FtcGxlX3BjdF9yZCApKQptaW5fc2FtcGxlX3BjdF9kYXRhICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKG5fc2VxKSx5PW1pbl9zYW1wbGVfcGN0KSkgKyBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSsgZ2VvbV9ib3hwbG90KCkKbWluX3RyYWluaW5nX3NpemVfZGF0YSA8LSBzcHJfZGF0YV9va19iZXN0X2RhdGEgJT4lIHN1bW1hcmlzZShtaW5fdHJhaW5pbmdfc2l6ZSA9IG1pbihhY3R1Y2FsX3RyYWluaW5nX3NpemUpKQptaW5fdHJhaW5pbmdfc2l6ZV9kYXRhICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKG5fc2VxKSx5PW1pbl90cmFpbmluZ19zaXplICkpICsgZ2VvbV9ib3hwbG90KCkKCiN0ZXN0LnRyYWluaW5nLnNpemU8LXNwcl9kYXRhX29rX2Jlc3RfZGF0YSAlPiUgc3VtbWFyaXNlKG1pbl90cmFpbmluZ19zaXplID0gbWluKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSkpICAKI3Rlc3Quc2FtcGxlLnBjdDwtc3ByX2RhdGFfb2tfYmVzdF9kYXRhICU+JSBzdW1tYXJpc2UobWluX3NhbXBsZV9wY3QgPSBtaW4oc2FtcGxlX3BjdCkpCiNsbS50cmFpbmluZy5zaXplPC1sbShsb2cobWluX3RyYWluaW5nX3NpemUpfmxvZyhuX2xvY2kpK25fc2VxK2F2Z19lbnRyb3B5KyBnYXBfcGN0K2NvbnN0YW50X3NpdGVzX3BjdCwgZGF0YT10ZXN0LnRyYWluaW5nLnNpemUpCiNsbS5zYW1wbGUucGN0PC1sbShsb2cobWluX3NhbXBsZV9wY3QpfmxvZyhuX2xvY2kpK25fc2VxK2F2Z19lbnRyb3B5KyBnYXBfcGN0K2NvbnN0YW50X3NpdGVzX3BjdCwgZGF0YT10ZXN0LnNhbXBsZS5wY3QpCgojc3VtbWFyeShsbS50cmFpbmluZy5zaXplKQojc3VtbWFyeShsbS5zYW1wbGUucGN0KQoKYGBgCgpgYGB7cn0Kc3ByX2RhdGFfb2tfYmVzdF9yb3dzCnNwcl9mdWxsX2RhdGFfYmVzdF9yb3dzICU+JSBkcGx5cjo6c2VsZWN0KHJlbGF0aXZlX3BhdGgsbl9sb2NpLCBhY3R1Y2FsX3RyYWluaW5nX3NpemUsIHNhbXBsZV9wY3RfcmQsZGVsdGFfbGxfZmlyc3RfcGhhc2UpCmBgYAoKCgpGb2N1c2luZyBvbiBkYXRhc2V0cyB3aXRoIDE1IHNlcXVlbmNlcywgd2hhdCBpcyB0aGUgYmVzdCBjdXR0b2ZmPwoKYGBge3J9CgpzcHJfZGF0YSAlPiVmaWx0ZXIobl9zZXE9PTE1KSU+JSBncm91cF9ieShhY3R1Y2FsX3RyYWluaW5nX3NpemUsIHNhbXBsZV9wY3RfcmQpICU+JSBzdW1tYXJpemUocGN0X2lkZW50aWNhbCA9IHN1bShyZl9kaXN0X2ZpcnN0X3BoYXNlPT0wKS9zdW0ocmZfZGlzdF9maXJzdF9waGFzZT09MCB8IHJmX2Rpc3RfZmlyc3RfcGhhc2U+MCksIGlkZW50aWNhbF90b3BvPSBzdW0ocmZfZGlzdF9maXJzdF9waGFzZT09MCksIGRpZmZfdG9wbyA9IHN1bShyZl9kaXN0X2ZpcnN0X3BoYXNlPjApLCBtYXhfcmYgPSBtYXgocmZfZGlzdF9maXJzdF9waGFzZSksIG1heF9kZWx0YV9sbD0gbWF4KGRlbHRhX2xsX2ZpcnN0X3BoYXNlKSxtZWRpYW5fZGVsdGFfbGw9IG1lZGlhbihkZWx0YV9sbF9maXJzdF9waGFzZVtyZl9kaXN0X2ZpcnN0X3BoYXNlPjBdKSxtZWFuX2RlbHRhX2xsID0gbWVhbihkZWx0YV9sbF9maXJzdF9waGFzZVtyZl9kaXN0X2ZpcnN0X3BoYXNlPjBdKSwgbWF4X3Nwcl9kaXN0ID0gbWF4KGxhc3NvX1NQUl9zZWNvbmRfcGhhc2Vfc3ByX21vdmVzKSwgKQoKCgpzcHJfZGF0YSAlPiVmaWx0ZXIobl9zZXE9PTE1KSAlPiUgZmlsdGVyKCEocmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSkgJT4lIGZpbHRlcihyZl9kaXN0X2ZpcnN0X3BoYXNlPjApICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKHNhbXBsZV9wY3RfcmQpLHk9IGRlbHRhX2xsX2ZpcnN0X3BoYXNlKSkgKyBnZW9tX2JveHBsb3QoKSArIGZhY2V0X3dyYXAofmFzLmZhY3RvcihhY3R1Y2FsX3RyYWluaW5nX3NpemUpKQoKc3ByX2RhdGEgJT4lZmlsdGVyKG5fc2VxPT0xNSkgJT4lIGZpbHRlcighKHJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykpICU+JSBmaWx0ZXIocmZfZGlzdF9maXJzdF9waGFzZT4wKSAlPiUgZ2dwbG90KGFlcyh4PWFzLmZhY3RvcihzYW1wbGVfcGN0X3JkKSx5PSBkZWx0YV9sbF9maXJzdF9waGFzZSkpICsgZ2VvbV9ib3hwbG90KCkgKyBmYWNldF93cmFwKH5hcy5mYWN0b3IoYWN0dWNhbF90cmFpbmluZ19zaXplKSkKCgoKCmBgYAoKCkZvY3VzaW5nIG9uIGRhdGFzZXRzIHdpdGggbGVzcyB0aGFuIDE1IHNlcXVlbmNlcywgd2hhdCBpcyB0aGUgYmVzdCBjdXR0b2ZmPwoKYGBge3J9CgpzcHJfZGF0YSAlPiVmaWx0ZXIobl9zZXE8MTUpJT4lIGdyb3VwX2J5KGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSwgc2FtcGxlX3BjdF9yZCkgJT4lIHN1bW1hcml6ZShwY3RfaWRlbnRpY2FsID0gc3VtKHJmX2Rpc3RfZmlyc3RfcGhhc2U9PTApL3N1bShyZl9kaXN0X2ZpcnN0X3BoYXNlPT0wIHwgcmZfZGlzdF9maXJzdF9waGFzZT4wKSwgaWRlbnRpY2FsX3RvcG89IHN1bShyZl9kaXN0X2ZpcnN0X3BoYXNlPT0wKSwgZGlmZl90b3BvID0gc3VtKHJmX2Rpc3RfZmlyc3RfcGhhc2U+MCksIG1heF9yZiA9IG1heChyZl9kaXN0X2ZpcnN0X3BoYXNlKSwgbWF4X2RlbHRhX2xsPSBtYXgoZGVsdGFfbGxfZmlyc3RfcGhhc2UpLG1lZGlhbl9kZWx0YV9sbD0gbWVkaWFuKGRlbHRhX2xsX2ZpcnN0X3BoYXNlW3JmX2Rpc3RfZmlyc3RfcGhhc2U+MF0pLG1lYW5fZGVsdGFfbGwgPSBtZWFuKGRlbHRhX2xsX2ZpcnN0X3BoYXNlW3JmX2Rpc3RfZmlyc3RfcGhhc2U+MF0pLCBtYXhfc3ByX2Rpc3QgPSBtYXgobGFzc29fU1BSX3NlY29uZF9waGFzZV9zcHJfbW92ZXMpLCApCgoKCnNwcl9kYXRhICU+JWZpbHRlcihuX3NlcTwxNSkgJT4lIGZpbHRlcihyZl9kaXN0X2ZpcnN0X3BoYXNlPjApICU+JSBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKHNhbXBsZV9wY3RfcmQpLHk9IGRlbHRhX2xsX2ZpcnN0X3BoYXNlKSkgKyBnZW9tX2JveHBsb3QoKSArZmFjZXRfd3JhcCh+YXMuZmFjdG9yKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSkpCgoKCgpgYGAKCgpgYGB7cn0KCnNwcl9kYXRhICU+JSBmaWx0ZXIoIXJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJT4lIGdncGxvdChhZXMoeD1gbGFzc29fdGVzdF9SXjJgLHkgPWBSXjJfcGVhcnNvbl9kdXJpbmdfdHJlZV9zZWFyY2hgICkpICtnZW9tX3BvaW50KCkKCnByaW50KGNvcihzcHJfZGF0YSRgbGFzc29fdGVzdF9SXjJgLCBzcHJfZGF0YSRgUl4yX3BlYXJzb25fZHVyaW5nX3RyZWVfc2VhcmNoYCkpCgpzcHJfZGF0YSAlPiUgZmlsdGVyKCFyZWxhdGl2ZV9wYXRoICVpbiUgcHJvYmxlbWF0aWNfZGF0YXNldHMpICU+JSBnZ3Bsb3QoYWVzKHg9YGxhc3NvX3Rlc3RfUl4yYCx5ID0gZGVsdGFfbGxfZmlyc3RfcGhhc2UgKSkgK2dlb21fcG9pbnQoKQoKcHJpbnQoY29yKHNwcl9kYXRhJGBsYXNzb190ZXN0X1JeMmAsIHNwcl9kYXRhJGRlbHRhX2xsX2ZpcnN0X3BoYXNlKSkKCnNwcl9kYXRhICU+JSBmaWx0ZXIoIXJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJT4lIGdncGxvdChhZXMoeD1gUl4yX3BlYXJzb25fZHVyaW5nX3RyZWVfc2VhcmNoYCx5ID0gZGVsdGFfbGxfZmlyc3RfcGhhc2UgKSkgK2dlb21fcG9pbnQoKQoKcHJpbnQoY29yKHNwcl9kYXRhJGBSXjJfcGVhcnNvbl9kdXJpbmdfdHJlZV9zZWFyY2hgLCBzcHJfZGF0YSRkZWx0YV9sbF9maXJzdF9waGFzZSApKQoKCnNwcl9kYXRhICU+JSBmaWx0ZXIoIXJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJT4lIGdncGxvdChhZXMoeD0gbWlzdGFrZV9jbnQseSA9IGRlbHRhX2xsX2ZpcnN0X3BoYXNlICkpICtnZW9tX3BvaW50KCkKCnByaW50KGNvcihzcHJfZGF0YSRtaXN0YWtlX2NudCwgc3ByX2RhdGEkZGVsdGFfbGxfZmlyc3RfcGhhc2UgKSkKYGBgCgoKTG9va2luZyBhdCBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseS4gSG93IGRlbHRhIGxsIGNoYW5nZXMgd2l0aCB0cmFpbmluZyBzaXplIGFuZCBzYW1wbGUgcGN0CgpgYGB7cn0KCmZvciAoZGF0YXNldCBpbiB1bmlxdWUoc3ByX2RhdGEkcmVsYXRpdmVfcGF0aCkpIHsKICAgCnByaW50KHNwcl9kYXRhICU+JWZpbHRlcihyZWxhdGl2ZV9wYXRoPT1kYXRhc2V0ICkgJT4lIGdncGxvdChhZXMoeD1zYW1wbGVfcGN0LHk9ZGVsdGFfbGxfZmlyc3RfcGhhc2UsIGdyb3VwID0gYXMuZmFjdG9yKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSksIGNvbG91ciA9IGFzLmZhY3RvcihhY3R1Y2FsX3RyYWluaW5nX3NpemUpKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSsgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkpICsgbGFicyh0aXRsZT1kYXRhc2V0KQogICAgIAp9CgpgYGAKTG9va2luZyBhdCBlYWNoIGRhdGFzZXQgc2VwYXJhdGVseS4gSG93IFIyIGNoYW5nZXMgd2l0aCB0cmFpbmluZyBzaXplIGFuZCBzYW1wbGUgcGN0CgpgYGB7cn0KZm9yIChkYXRhc2V0IGluIHVuaXF1ZShzcHJfZGF0YSRyZWxhdGl2ZV9wYXRoKSkgewogICAKcHJpbnQoc3ByX2RhdGEgJT4lZmlsdGVyKHJlbGF0aXZlX3BhdGg9PWRhdGFzZXQgKSAlPiUgZ2dwbG90KGFlcyh4PXNhbXBsZV9wY3QseT1gbGFzc29fdGVzdF9SXjJgLCBncm91cCA9IGFzLmZhY3RvcihhY3R1Y2FsX3RyYWluaW5nX3NpemUpLCBjb2xvdXIgPSBhcy5mYWN0b3IoYWN0dWNhbF90cmFpbmluZ19zaXplKSkpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkrIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpKSArIGxhYnModGl0bGU9ZGF0YXNldCkKICAgICAKfQpgYGAKCgpBbGwgZGF0YXNldHMgd2l0aCAxNSBzZXF1ZW5jZXMKCmBgYHtyfQoKCnNwcl9kYXRhICU+JWZpbHRlcighKHJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJiAoYWN0dWNhbF90cmFpbmluZ19zaXplPT04MDApICYgKG5fc2VxPT0xNSkpICU+JSBnZ3Bsb3QoYWVzKHg9c2FtcGxlX3BjdF9yZCx5PWRlbHRhX2xsX2ZpcnN0X3BoYXNlLCBncm91cD0gcmVsYXRpdmVfcGF0aCwgY29sb3VyID0gcmVsYXRpdmVfcGF0aCkpICsgZ2VvbV9saW5lKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkrbGFicyh0aXRsZT0idHJhaW5pbmcgc2l6ZSA4MDAiKQoKc3ByX2RhdGEgJT4lZmlsdGVyKCEocmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAmIChhY3R1Y2FsX3RyYWluaW5nX3NpemU9PTQwMCkgJiAobl9zZXE9PTE1KSkgJT4lIGdncGxvdChhZXMoeD1zYW1wbGVfcGN0X3JkLHk9ZGVsdGFfbGxfZmlyc3RfcGhhc2UsIGdyb3VwPSByZWxhdGl2ZV9wYXRoLCBjb2xvdXIgPSByZWxhdGl2ZV9wYXRoKSkgKyBnZW9tX2xpbmUoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KStsYWJzKHRpdGxlPSJ0cmFpbmluZyBzaXplIDQwMCIpCgpzcHJfZGF0YSAlPiVmaWx0ZXIoIShyZWxhdGl2ZV9wYXRoICVpbiUgcHJvYmxlbWF0aWNfZGF0YXNldHMpICYgKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZT09MjAwKSAmIG5fc2VxPT0xNSkgJT4lIGdncGxvdChhZXMoeD1zYW1wbGVfcGN0X3JkLHk9ZGVsdGFfbGxfZmlyc3RfcGhhc2UsIGdyb3VwPSByZWxhdGl2ZV9wYXRoLCBjb2xvdXIgPSByZWxhdGl2ZV9wYXRoKSkgKyBnZW9tX2xpbmUoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KStsYWJzKHRpdGxlPSJ0cmFpbmluZyBzaXplIDIwMCIpCgpzcHJfZGF0YSAlPiVmaWx0ZXIoIShyZWxhdGl2ZV9wYXRoICVpbiUgcHJvYmxlbWF0aWNfZGF0YXNldHMpICYgKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZT09MTAwKSAmIG5fc2VxPT0xNSkgJT4lIGdncGxvdChhZXMoeD1zYW1wbGVfcGN0X3JkLHk9ZGVsdGFfbGxfZmlyc3RfcGhhc2UsIGdyb3VwPSByZWxhdGl2ZV9wYXRoLCBjb2xvdXIgPSByZWxhdGl2ZV9wYXRoKSkgKyBnZW9tX2xpbmUoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KStsYWJzKHRpdGxlPSJ0cmFpbmluZyBzaXplIDEwMCIpCgoKCmBgYAoKTGVzcyB0aGFuIDE1IHNlcXVlbmNlcwoKYGBge3J9CgoKc3ByX2RhdGEgJT4lZmlsdGVyKCEocmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAmIChhY3R1Y2FsX3RyYWluaW5nX3NpemU9PTgwMCkgJiAobl9zZXE8MTUpKSAlPiUgZ2dwbG90KGFlcyh4PXNhbXBsZV9wY3QseT1kZWx0YV9sbF9maXJzdF9waGFzZSwgZ3JvdXA9IHJlbGF0aXZlX3BhdGgsIGNvbG91ciA9IHJlbGF0aXZlX3BhdGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZSgpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkrbGFicyh0aXRsZT0idHJhaW5pbmcgc2l6ZSA4MDAiKQoKc3ByX2RhdGEgJT4lZmlsdGVyKCEocmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAmIChhY3R1Y2FsX3RyYWluaW5nX3NpemU9PTQwMCkgJiAobl9zZXE8MTUpKSAlPiUgZ2dwbG90KGFlcyh4PXNhbXBsZV9wY3QseT1kZWx0YV9sbF9maXJzdF9waGFzZSwgZ3JvdXA9IHJlbGF0aXZlX3BhdGgsIGNvbG91ciA9IHJlbGF0aXZlX3BhdGgpKSArIGdlb21fbGluZSgpKyBnZW9tX3BvaW50KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkrbGFicyh0aXRsZT0idHJhaW5pbmcgc2l6ZSA0MDAiKQoKc3ByX2RhdGEgJT4lZmlsdGVyKCEocmVsYXRpdmVfcGF0aCAlaW4lIHByb2JsZW1hdGljX2RhdGFzZXRzKSAmIChhY3R1Y2FsX3RyYWluaW5nX3NpemU9PTIwMCkgJiBuX3NlcTwxNSkgJT4lIGdncGxvdChhZXMoeD1zYW1wbGVfcGN0LHk9ZGVsdGFfbGxfZmlyc3RfcGhhc2UsIGdyb3VwPSByZWxhdGl2ZV9wYXRoLCBjb2xvdXIgPSByZWxhdGl2ZV9wYXRoKSkgKyBnZW9tX2xpbmUoKSsgZ2VvbV9wb2ludCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpK2xhYnModGl0bGU9InRyYWluaW5nIHNpemUgMjAwIikKCnNwcl9kYXRhICU+JWZpbHRlcighKHJlbGF0aXZlX3BhdGggJWluJSBwcm9ibGVtYXRpY19kYXRhc2V0cykgJiAoYWN0dWNhbF90cmFpbmluZ19zaXplPT0xMDApICYgbl9zZXE8MTUpICU+JSBnZ3Bsb3QoYWVzKHg9c2FtcGxlX3BjdCx5PWRlbHRhX2xsX2ZpcnN0X3BoYXNlLCBncm91cD0gcmVsYXRpdmVfcGF0aCwgY29sb3VyID0gcmVsYXRpdmVfcGF0aCkpICsgZ2VvbV9saW5lKCkrIGdlb21fcG9pbnQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KStsYWJzKHRpdGxlPSJ0cmFpbmluZyBzaXplIDEwMCIpCgoKCmBgYAoKCgpgYGB7cn0Kc3ByX2RhdGFfZGF0YXNldHNfdGVzdDwtIHNwcl9kYXRhICU+JSBmaWx0ZXIoZGVsdGFfbGxfZmlyc3RfcGhhc2U+MCAmIGFjdHVjYWxfdHJhaW5pbmdfc2l6ZT09ODAwICYgc2FtcGxlX3BjdF9yZD09MC4yKSAgJT4lIHB1bGwocmVsYXRpdmVfcGF0aCkKCgoKZm9yIChkYXRhc2V0IGluIHNwcl9kYXRhX2RhdGFzZXRzX3Rlc3QpIHsKICAgCnByaW50KHNwcl9kYXRhICU+JWZpbHRlcihyZWxhdGl2ZV9wYXRoPT1kYXRhc2V0ICkgJT4lIGdncGxvdChhZXMoeD1zYW1wbGVfcGN0LHk9ZGVsdGFfbGxfZmlyc3RfcGhhc2UsIGdyb3VwID0gYXMuZmFjdG9yKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSksIGNvbG91ciA9IGFzLmZhY3RvcihhY3R1Y2FsX3RyYWluaW5nX3NpemUpKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSsgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkpICsgbGFicyh0aXRsZT1kYXRhc2V0KQojcHJpbnQoc3ByX2RhdGEgJT4lZmlsdGVyKHJlbGF0aXZlX3BhdGg9PWRhdGFzZXQgKSAlPiUgZ2dwbG90KGFlcyh4PXNhbXBsZV9wY3QseT1gbGFzc29fdGVzdF9SXjJgLCBncm91cCA9IGFzLmZhY3RvcihhY3R1Y2FsX3RyYWluaW5nX3NpemUpLCBjb2xvdXIgPSAjYXMuZmFjdG9yKGFjdHVjYWxfdHJhaW5pbmdfc2l6ZSkpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpKyBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSkgKyBsYWJzKHRpdGxlPWRhdGFzZXQpCiNwcmludChzcHJfZGF0YSAlPiVmaWx0ZXIocmVsYXRpdmVfcGF0aD09ZGF0YXNldCApICU+JSBnZ3Bsb3QoYWVzKHg9c2FtcGxlX3BjdCx5PWBSXjJfcGVhcnNvbl9kdXJpbmdfdHJlZV9zZWFyY2hgLCBncm91cCA9IGFzLmZhY3RvcihhY3R1Y2FsX3RyYWluaW5nX3NpemUpLCBjb2xvdXIgPSBhcy5mYWN0b3IoYWN0dWNhbF90cmFpbmluZ19zaXplKSkpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkrIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpKSArIGxhYnModGl0bGU9ZGF0YXNldCkKICAgICAKfQoKCgpzcHJfZGF0YSAlPiUgZmlsdGVyIChyZWxhdGl2ZV9wYXRoICVpbiUgc3ByX2RhdGFfZGF0YXNldHNfdGVzdCkgJT4lIGRpc3RpbmN0IChyZWxhdGl2ZV9wYXRoLG5fbG9jaSwgbl9zZXEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmBgYAoKCgoKYGBge3J9CmxpYnJhcnkobmxtZSkKbGFzc29fc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChzcHJfZGF0YV9vaywgcHJvcCA9IDAuNzUpCmxhc3NvX3RyYWluaW5nIDwtIGxhc3NvX3NwbGl0ICAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nKCkKbGFzc29fdGVzdDwtIGxhc3NvX3NwbGl0ICAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RpbmcoKQoKCm1vZGVsLmV4cG9uZW50aWFsPC1sbWUoKGRlbHRhX2xsX2ZpcnN0X3BoYXNlKX5hY3R1Y2FsX3RyYWluaW5nX3NpemUrIHNhbXBsZV9wY3QrZGl2ZXJnZW5jZSttYWQrZ2FwX3BjdCtudW1iZXJfbG9jaV9jaG9zZW4rbnVtYmVyX2xvY2lfY2hvc2VuK25fbG9jaSxyYW5kb209fjF8cmVsYXRpdmVfcGF0aCwgZGF0YT1zcHJfZGF0YV9vaykKcGxvdChtb2RlbC5leHBvbmVudGlhbCkKcHJpbnQoc3VtbWFyeShtb2RlbC5leHBvbmVudGlhbCkpCnByaW50KGludGVydmFscyhtb2RlbC5leHBvbmVudGlhbCx3aGljaCA9ICJmaXhlZCIpKQpxcW5vcm0obW9kZWwuZXhwb25lbnRpYWwsIH5yYW5lZiguLCBsZXZlbD0xKSkKCgpgYGAKCgpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ21kK09wdGlvbitJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ21kK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuIAoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLgoK